package cn.yonghui.hyd.plugin

import org.objectweb.asm.AnnotationVisitor
import org.objectweb.asm.Handle
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.commons.AdviceAdapter

class DemoMethodVisitor extends AdviceAdapter {

    String mClassName
    String mSuperName
    private String[] mInterfaces
    HashSet<String> visitedMethods

    static StringBuilder installedPackages = new StringBuilder()

    DemoMethodVisitor(MethodVisitor mv,
                      int access, String name, String desc,
                      String className, String superName,
                      String[] interfaces, HashSet<String> visitedMethods) {
        super(Opcodes.ASM6, mv, access, name, desc)
        this.mClassName = className
        this.mSuperName = superName
        this.mInterfaces = interfaces
        this.visitedMethods = visitedMethods
    }

    @Override
    void visitFieldInsn(int opcode, String owner, String name, String desc) {
        super.visitFieldInsn(opcode, owner, name, desc)
    }

    @Override
    void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
        super.visitMethodInsn(opcode, owner, name, desc, itf)

        if (("getInstalledPackages" == name || "getInstalledApplications" == name)
                && (owner == "android/content/pm/PackageManager"
                || owner == "android/app/ApplicationPackageManager")) {
            if (!installedPackages.contains(mClassName)) {
                installedPackages.append(mClassName).append("\n")
            }
            installedAppsCallback()
        }
    }

    @Override
    void visitInvokeDynamicInsn(String name1, String desc1, Handle bsm, Object... bsmArgs) {
        super.visitInvokeDynamicInsn(name1, desc1, bsm, bsmArgs)
    }

    @Override
    protected void onMethodEnter() {
        super.onMethodEnter()
        def nameDesc = name + this.methodDesc
        if (nameDesc == 'onClick(Landroid/view/View;)V'
                && isPublic(access)
                && !isAbstract(access)
                && !isStatic(access)) {
            println("======onClick(Landroid/view/View;)V====" + mClassName)
            mv.visitVarInsn(ALOAD, 1)
            mv.visitMethodInsn(INVOKESTATIC, "cn/yonghui/hyd/ASMHelper", "trackViewOnClick", "(Landroid/view/View;)V", false)
        }
    }

    @Override
    protected void onMethodExit(int opcode) {
        super.onMethodExit(opcode)

        def nameDesc = name + this.methodDesc
        if (nameDesc == 'onClick(Landroid/view/View;)V'
                && isPublic(access)
                && !isAbstract(access)
                && !isStatic(access)) {
            mv.visitVarInsn(ALOAD, 1)
            mv.visitMethodInsn(INVOKESTATIC, "cn/yonghui/hyd/ASMHelper", "trackViewAfterOnClick", "(Landroid/view/View;)V", false)
        }
    }

    /**
     * 该方法是当扫描器扫描到类注解声明时进行调用
     *
     * @param s 注解的类型。它使用的是（“L” + “类型路径” + “;”）形式表述
     * @param b 表示的是，该注解是否在 JVM 中可见
     * 1.RetentionPolicy.SOURCE：声明注解只保留在 Java 源程序中，在编译 Java 类时注解信息不会被写入到 Class。如果使用的是这个配置 ASM 也将无法探测到这个注解。
     * 2.RetentionPolicy.CLASS：声明注解仅保留在 Class 文件中，JVM 运行时并不会处理它，这意味着 ASM 可以在 visitAnnotation 时候探测到它，但是通过Class 反射无法获取到注解信息。
     * 3.RetentionPolicy.RUNTIME：这是最常用的一种声明，ASM 可以探测到这个注解，同时 Java 反射也可以取得注解的信息。所有用到反射获取的注解都会用到这个配置，就是这个原因。
     * @return
     */
    @Override
    AnnotationVisitor visitAnnotation(String s, boolean b) {
        return super.visitAnnotation(s, b)
    }

    private void installedAppsCallback() {
        mv.visitLdcInsn(mClassName)
        mv.visitMethodInsn(INVOKESTATIC, "cn/yonghui/hyd/ASMHelper", "onInvokeInstalledApps",
                "(Ljava/lang/String;)V", false)
    }

    static boolean isPublic(int access) {
        return (access & Opcodes.ACC_PUBLIC) != 0
    }

    static boolean isStatic(int access) {
        return (access & Opcodes.ACC_STATIC) != 0
    }

    static boolean isAbstract(int access) {
        return (access & Opcodes.ACC_ABSTRACT) != 0
    }

}